﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using System.Xml.XPath;
using System.IO;
using System.Globalization;
using System.Diagnostics;
using DocumentFormat.OpenXml.Validation;

namespace fleXdoc.Api
{
    internal abstract class BaseTemplateProcessor : IDisposable
    {
        private bool _disposed = false;

        #region Constructor(s) and destructor
        public BaseTemplateProcessor()
            : base()
        {
        }

        ~BaseTemplateProcessor()
        {
            Dispose(false);
        }
        #endregion

        #region Public methods

        /// <summary>
        /// Proces the WordprocessingDocument
        /// </summary>
        /// <param name="doc">The template-instance to be processed</param>
        public void ProcessWordprocessingDocument(WordprocessingDocument doc, BuildOptions options)
        {
            // Attempt to read it from the template itself (fsNamespace)
            if (doc.CustomFilePropertiesPart != null)
            {
                DocumentFormat.OpenXml.CustomProperties.CustomDocumentProperty property =
                    (from p
                     in doc.CustomFilePropertiesPart.Properties.Cast<DocumentFormat.OpenXml.CustomProperties.CustomDocumentProperty>()
                     where p.Name.ToString().ToLowerInvariant() == "fdnamespaceuri"
                     select p).FirstOrDefault();

                // Only overwrite when explictly set by property
                options.NamespaceUri = ((property != null && property.VTLPWSTR != null) ? property.VTLPWSTR.Text : options.NamespaceUri);

                property =
                    (from p
                     in doc.CustomFilePropertiesPart.Properties.Cast<DocumentFormat.OpenXml.CustomProperties.CustomDocumentProperty>()
                     where p.Name.ToString().ToLowerInvariant() == "fdnamespaceprefix"
                     select p).FirstOrDefault();

                // Only overwrite when explictly set by property
                options.NamespacePrefix = ((property != null && property.VTLPWSTR != null) ? property.VTLPWSTR.Text : options.NamespacePrefix);
            }

            if (String.IsNullOrEmpty(options.NamespacePrefix)) // Still not set
            {
                options.NamespacePrefix = "d"; // Default prefix
            }

            if (String.IsNullOrEmpty(options.NamespaceUri))
            {
                // Take namespaceURI from context-node
                if (options.Data.NodeType == XPathNodeType.Root)
                {
                    options.Data.MoveToChild(XPathNodeType.Element); // Root itself is not usefull, so move to its first (and only) child-element
                    options.NamespaceUri = options.Data.NamespaceURI;
                    options.Data.MoveToRoot();
                }
                else
                {
                    options.NamespaceUri = options.Data.NamespaceURI;
                }
            }

            PreProcess(doc, options);
            Process(doc, options);
            PostProcess(doc, options);

            if (options.ValidatePackage)
            {
                ValidateWordprocessingDocument(doc);
            }
        }
        #endregion

        #region Protected methods
        protected virtual void ValidateWordprocessingDocument(WordprocessingDocument doc)
        {
            StringBuilder sb = new StringBuilder("Package validation errors/warnings:");
            bool errorsFound = false;

            OpenXmlValidator ooxmlVal = new OpenXmlValidator(FileFormatVersions.Office2007); // 2010 not yet supported
            IEnumerable<ValidationErrorInfo> valErrors = ooxmlVal.Validate(doc);
            foreach (ValidationErrorInfo valError in valErrors)
            {
                errorsFound = true;
                sb.AppendLine(String.Format(CultureInfo.InvariantCulture, "{0} (xpath: {1}): [{2}] {3}", valError.Part.Uri, valError.Path.XPath, valError.ErrorType, valError.Description));
            }

            if (errorsFound) // Validation errors found
            {
                throw new InvalidDataException(sb.ToString());
            }
        }

        protected string OnResourceUriResolve(string resourceUri)
        {
            if (ResourceUriResolve != null)
            {
                string resolvedResourceUri = ResourceUriResolve(this, new ResolveResourceUriEventArgs(resourceUri));
                if (!String.IsNullOrEmpty(resolvedResourceUri))
                {
                    // Return the resolved uri
                    return resolvedResourceUri;
                }
            }

            // Not resolved, so just return the original resource uri
            return resourceUri;
        }

        protected virtual void PreProcessWordprocessingDocument(WordprocessingDocument doc, BuildOptions options) { }
        protected virtual void PreProcessOpenXmlPart(WordprocessingDocument doc, OpenXmlPart part, BuildOptions options) { }
        protected virtual void PreProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options) { }
        protected virtual void ProcessOpenXmlPart(WordprocessingDocument doc, OpenXmlPart part, BuildOptions options) { }
        protected virtual void ProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options) { }
        protected virtual void PostProcessOpenXmlPart(WordprocessingDocument doc, OpenXmlPart part, BuildOptions options) { }
        protected virtual void PostProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options) { }
        protected virtual void PostProcessWordprocessingDocument(WordprocessingDocument doc, BuildOptions options) { }
        #endregion

        #region Delegates and events
        public event ResolveResourceUriEventHandler ResourceUriResolve;
        delegate void ProcessCustomXmlDelegate(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options);
        #endregion

        #region Private methods
        private void PreProcess(WordprocessingDocument doc, BuildOptions options)
        {
            Debug.WriteLine("BaseTemplateProcessor: PreProcess:" + doc.PackageProperties.Title);

            // Document
            PreProcessWordprocessingDocument(doc, options);

            // Parts & CustomXml-elements
            IList<HeaderPart> headerParts = doc.MainDocumentPart.HeaderParts.ToList();
            for (int index = 0; index < headerParts.Count; index++)
            {
                OpenXmlPart part = headerParts[index];
                HandleCustomXmlElements(doc, part, PreProcessCustomXmlElement, options);
                PreProcessOpenXmlPart(doc, part, options);
            }
            IList<FooterPart> footerParts = doc.MainDocumentPart.FooterParts.ToList();
            for (int index = 0; index < footerParts.Count; index++)
            {
                OpenXmlPart part = footerParts[index];
                HandleCustomXmlElements(doc, part, PreProcessCustomXmlElement, options);
                PreProcessOpenXmlPart(doc, part, options);
            }
            HandleCustomXmlElements(doc, doc.MainDocumentPart, PreProcessCustomXmlElement, options);
            PreProcessOpenXmlPart(doc, doc.MainDocumentPart, options);
        }

        private void Process(WordprocessingDocument doc, BuildOptions options)
        {
            Debug.WriteLine("BaseTemplateProcessor: Process:" + doc.PackageProperties.Title);

            // Parts & CustomXml-elements
            IList<HeaderPart> headerParts = doc.MainDocumentPart.HeaderParts.ToList();
            for (int index = 0; index < headerParts.Count; index++)
            {
                OpenXmlPart part = headerParts[index];
                HandleCustomXmlElements(doc, part, ProcessCustomXmlElement, options);
                ProcessOpenXmlPart(doc, part, options);
            }
            IList<FooterPart> footerParts = doc.MainDocumentPart.FooterParts.ToList();
            for (int index = 0; index < footerParts.Count; index++)
            {
                OpenXmlPart part = footerParts[index];
                HandleCustomXmlElements(doc, part, ProcessCustomXmlElement, options);
                ProcessOpenXmlPart(doc, part, options);
            }
            HandleCustomXmlElements(doc, doc.MainDocumentPart, ProcessCustomXmlElement, options);
            ProcessOpenXmlPart(doc, doc.MainDocumentPart, options);
        }

        private void PostProcess(WordprocessingDocument doc, BuildOptions options)
        {
            Debug.WriteLine("BaseTemplateProcessor: PostProcess:" + doc.PackageProperties.Title);

            // Parts & CustomXml-elements
            IList<HeaderPart> headerParts = doc.MainDocumentPart.HeaderParts.ToList();
            for (int index = 0; index < headerParts.Count; index++)
            {
                OpenXmlPart part = headerParts[index];
                HandleCustomXmlElements(doc, part, PostProcessCustomXmlElement, options);
                PostProcessOpenXmlPart(doc, part, options);
            }
            IList<FooterPart> footerParts = doc.MainDocumentPart.FooterParts.ToList();
            for (int index = 0; index < footerParts.Count; index++)
            {
                OpenXmlPart part = footerParts[index];
                HandleCustomXmlElements(doc, part, PostProcessCustomXmlElement, options);
                PostProcessOpenXmlPart(doc, part, options);
            }
            HandleCustomXmlElements(doc, doc.MainDocumentPart, PostProcessCustomXmlElement, options);
            PostProcessOpenXmlPart(doc, doc.MainDocumentPart, options);

            // Document
            PostProcessWordprocessingDocument(doc, options);
        }

        private void HandleCustomXmlElements(WordprocessingDocument doc, OpenXmlPart part, ProcessCustomXmlDelegate processCustomXml, BuildOptions options)
        {
            foreach (CustomXmlElement xmlElement in part.RootElement.Descendants<CustomXmlElement>().ToList())
            {
                processCustomXml(doc, part, xmlElement, xmlElement.Uri, xmlElement.Element.Value, xmlElement.Descendants<CustomXmlProperties>().FirstOrDefault(), options);
            }
            processCustomXml = null;
        }
        #endregion

        #region IDisposable Members
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    GC.SuppressFinalize(this);
                    // Dispose managed resources
                }
                _disposed = true;
            }
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
        }
        #endregion
    }
}
